"""
语音服务实现
"""
import os
import uuid
import time
import threading
import requests
import re
import shutil
import traceback
from typing import Dict, Any, List, Optional, Tuple, Callable

from app.services.speech_service import ISpeechService
from app.services.audio_service import IAudioService
from app.services.base_service import Event
from app.models.audio_item import AudioItem
from app.utils.logger import Logger
from app.config.app_config import AppConfig
from app.services.service_locator import ServiceLocator
from app.services.config_service import IConfigService


class SpeechService(ISpeechService):
    """
    语音服务实现
    """
    
    def __init__(self):
        """
        初始化语音服务
        """
        super().__init__()
        
        # 获取日志记录器
        self.logger = Logger.get_logger("speech_service")
        
        # 音频服务
        self._audio_service: Optional[IAudioService] = None
        
        # 默认音色
        self._default_speaker = "default"
        
        # 配置
        self._config: Dict[str, Any] = {
            "cache_dir": "音频缓存",
            "default_speaker": "default"
        }
        
        # 合成线程池
        self._synthesis_threads: List[threading.Thread] = []
        
        # 批量生成线程池
        self._batch_threads: List[threading.Thread] = []
        
        # 最大线程数 - 限制为1，确保同时只处理一个音频生成任务
        self._max_threads = 1
        
        # 最大批量生成线程数
        self._max_batch_threads = 1
        
        # 是否正在运行
        self._running = False
        
        # 事件监听器字典，键为事件类型，值为监听器列表
        self._listeners: Dict[str, List[Callable[[Event], None]]] = {}
        
        # 初始化临时辅助对象，用于线程间通信
        self._temp_helper = None
        self._temp_helper_single = None
        
        # 创建信号发射器，用于线程间通信
        from PyQt5.QtCore import QObject, pyqtSignal
        
        class SignalEmitter(QObject):
            batch_completion_signal = pyqtSignal(object, object)  # 批量完成信号
            single_completion_signal = pyqtSignal(str, object)    # 单个完成信号
        
        self._signal_emitter = SignalEmitter()
        
        self.logger.info("语音服务初始化完成")
    
    def _get_audio_cache_dir(self) -> str:
        """
        获取音频缓存目录路径
        
        @return: 音频缓存目录路径
        """
        # 从配置服务获取app.config_path
        config_service = ServiceLocator.get(IConfigService)
        app_config_path = ""
        
        if config_service:
            # 直接从配置服务获取app.config_path
            app_config_path = config_service.get("app.config_path", "")
            self.logger.debug(f"从配置服务获取app.config_path: {app_config_path}")
        
        if not app_config_path:
            # 如果配置服务未获取到，尝试从AppConfig获取
            config = AppConfig.get_instance()
            app_config_obj = config.get("app", {})
            if isinstance(app_config_obj, dict) and "config_path" in app_config_obj:
                app_config_path = app_config_obj["config_path"]
                self.logger.debug(f"从AppConfig获取app.config_path: {app_config_path}")
        
        if app_config_path:
            # 使用app.config_path/音频缓存作为缓存目录
            cache_dir = os.path.join(app_config_path, "音频缓存")
            self.logger.debug(f"使用配置路径下的音频缓存目录: {cache_dir}")
        else:
            # 如果app.config_path未设置，使用默认缓存目录
            cache_dir = self._config.get("cache_dir", "音频缓存")
            self.logger.debug(f"使用默认音频缓存目录: {cache_dir}")
            
        # 确保目录存在
        os.makedirs(cache_dir, exist_ok=True)
        
        return cache_dir
    
    def add_listener(self, event_type: str, listener: Callable[[Event], None]) -> None:
        """
        添加事件监听器
        
        @param event_type: 事件类型
        @param listener: 监听器函数，接收一个 Event 参数
        """
        if event_type not in self._listeners:
            self._listeners[event_type] = []
        
        if listener not in self._listeners[event_type]:
            self._listeners[event_type].append(listener)
    
    def remove_listener(self, event_type: str, listener: Callable[[Event], None]) -> bool:
        """
        移除事件监听器
        
        @param event_type: 事件类型
        @param listener: 监听器函数
        @return: 是否成功移除
        """
        if event_type in self._listeners and listener in self._listeners[event_type]:
            self._listeners[event_type].remove(listener)
            return True
        return False
    
    def clear_listeners(self, event_type: str = None) -> None:
        """
        清除事件监听器
        
        @param event_type: 事件类型，如果为 None 则清除所有监听器
        """
        if event_type is None:
            self._listeners.clear()
        elif event_type in self._listeners:
            self._listeners[event_type].clear()
    
    def notify(self, event_type: str, data: Any = None) -> None:
        """
        通知事件
        
        @param event_type: 事件类型
        @param data: 事件数据
        """
        # 创建事件对象
        event = Event(event_type, data)
        
        # 调用监听器
        if event_type in self._listeners:
            for listener in self._listeners[event_type]:
                try:
                    listener(event)
                except Exception as e:
                    # 这里可以添加日志记录
                    self.logger.error(f"事件监听器异常: {str(e)}")
    
    def _sanitize_filename(self, text: str) -> str:
        """
        处理文件名，去除非法字符
        
        @param text: 原始文本
        @return: 处理后的文件名
        """
        # 替换非法字符
        # 只保留字母、数字、下划线、中文字符
        return re.sub(r'[^\w\u4e00-\u9fff]', '_', text)
    
    def _ensure_speaker_format(self, speaker: str) -> str:
        """
        确保音色参数格式正确（根据配置决定是否包含.pt后缀）
        
        @param speaker: 音色名称
        @return: 处理后的音色名称
        """
        # 检查配置是否需要添加.pt后缀
        add_pt_suffix = True  # 默认添加
        
        # 从配置服务获取配置
        config_service = ServiceLocator.get(IConfigService)
        if config_service:
            add_pt_suffix = config_service.get("speech.add_pt_suffix", True)
        elif "add_pt_suffix" in self._config:
            add_pt_suffix = self._config["add_pt_suffix"]
        
        # 根据配置决定是否添加.pt后缀
        if add_pt_suffix and speaker and not speaker.lower().endswith('.pt'):
            return f"{speaker}.pt"
        return speaker
    
    def synthesize(self, text: str, speaker: str, output_dir: Optional[str] = None, filename: Optional[str] = None) -> Optional[str]:
        """
        合成语音
        
        @param text: 要合成的文本
        @param speaker: 音色ID，必填参数
        @param output_dir: 输出目录，如果为 None 则使用配置中的app.config_path/音频缓存目录
        @param filename: 输出文件名（不含扩展名），如果为 None 则使用处理后的文本内容
        @return: 生成的音频文件路径，如果合成失败则返回 None
        """
        try:
            # 检查speaker参数是否提供
            if not speaker:
                self.logger.error("合成语音失败: speaker参数为必填项")
                self.notify("speech_synthesis_failed", {"text": text, "error": "speaker参数为必填项"})
                return None
            
            # 生成记录 ID
            record_id = str(uuid.uuid4())
            
            # 确定输出目录
            if output_dir is None:
                # 使用辅助方法获取音频缓存目录
                output_dir = self._get_audio_cache_dir()
            
            # 确保输出目录存在
            os.makedirs(output_dir, exist_ok=True)
            
            # 确定文件名
            if filename is None:
                # 注：文本已在UI层处理完毕，这里直接使用
                
                # 使用处理后的文本内容作为文件名
                # 限制长度，避免文件名过长
                text_for_filename = text[:20]  # 取前20个字符
                filename = self._sanitize_filename(text_for_filename)
                
                # 添加音色信息和时间戳确保唯一性
                speaker_short = self._sanitize_filename(speaker)[:10]  # 取音色名称的前10个字符
                timestamp = int(time.time())
                filename = f"{filename}_{speaker_short}_{timestamp}"
            
            # 生成文件路径
            file_path = os.path.join(output_dir, f"{filename}.wav")
            
            # 触发语音合成开始事件
            self.notify("speech_synthesis_started", {"text": text, "file_path": file_path})
            
            # 优先使用内部_config中的tts_url配置
            tts_url = self._config.get("tts_url")
            
            # 如果内部配置没有，则从AppConfig中获取TTS服务URL
            if not tts_url:
                config = AppConfig.get_instance()
                tts_url = config.get("tts", "url", "http://127.0.0.1:8080")
            
            timeout = self._config.get("tts_timeout", 30)
            if not timeout:
                config = AppConfig.get_instance()
                timeout = config.get("tts", "timeout", 30)
            
            # 从配置服务获取语速设置
            config_service = ServiceLocator.get(IConfigService)
            speed = 1.0  # 默认语速为1.0
            if config_service:
                # 获取主播语速配置，值为整数（范围30-200），需要转换为浮点数（0.3-2.0）
                host_speed_int = config_service.get("speech.host_speed", 100)
                speed = host_speed_int / 100.0
            else:
                # 从AppConfig获取
                config = AppConfig.get_instance()
                host_speed_int = config.get("speech", "host_speed", 100)
                if isinstance(host_speed_int, int):
                    speed = host_speed_int / 100.0
            
            # 记录使用的实际音色名称
            actual_speaker = self._ensure_speaker_format(speaker)
            
            self.logger.info(f"开始合成语音，使用URL: {tts_url}, 音色: {actual_speaker}, 语速: {speed:.2f}")
            
            # 记录开始时间
            start_time = time.time()
            
            # 准备请求数据
            data = {
                "text": text,
                "speaker": self._ensure_speaker_format(speaker),  # 确保音色参数包含.pt后缀
                "speed": speed       # 语速参数
            }
            
            # 发送GET请求
            response = requests.get(tts_url, params=data, timeout=timeout)
            
            # 记录结束时间和耗时
            end_time = time.time()
            elapsed_time = end_time - start_time
            
            self.logger.info(f"文本转语音耗时: {elapsed_time:.2f} 秒, 状态码: {response.status_code}")
            
            # 检查响应状态
            if response.status_code == 200:
                # 将响应内容写入文件
                with open(file_path, "wb") as f:
                    f.write(response.content)
                
                # 检查是否需要处理音频空白部分
                config_service = ServiceLocator.get(IConfigService)
                if config_service:
                    # 获取音频空白处理配置
                    trim_enabled = config_service.get("audio.trim_enabled", True)
                    trim_threshold = config_service.get("audio.trim_threshold", 0.02)
                    
                    if trim_enabled:
                        self.logger.info(f"执行音频空白处理，阈值: {trim_threshold}, 文件: {file_path}")
                        try:
                            from app.utils.audio.audio_processor import AudioProcessor
                            # 使用AudioProcessor处理文件（覆盖原文件）
                            AudioProcessor.trim_silence(file_path, threshold=trim_threshold, save_to_new_file=False)
                        except Exception as e:
                            self.logger.error(f"音频空白处理失败: {str(e)}")
                            # 失败不影响后续流程，继续使用原文件
                
                # 触发语音合成完成事件
                self.notify("speech_synthesized", {"text": text, "file_path": file_path})
            
                # 如果设置了音频服务，则播放语音
                if self._audio_service:
                    # 创建音频项
                    audio_item = AudioItem(
                        id=record_id,
                        file_path=file_path,
                        title=f"语音: {text[:20]}...",
                        timestamp=time.strftime("%Y-%m-%dT%H:%M:%S"),
                        source_type="speech",
                        source_id=record_id,
                        metadata={"speaker": speaker, "text": text}  # 添加更多元数据
                    )
                    
                    # 添加到播放队列
                    self._audio_service.add_to_queue(audio_item)
                
                return file_path
            elif response.status_code == 400:
                # 参数错误
                error_content = response.text
                self.logger.error(f"TTS服务参数错误: {error_content}")
                self.notify("speech_synthesis_failed", {"text": text, "error": f"参数错误: {error_content}"})
                return None
            elif response.status_code == 401:
                # 未授权
                self.logger.error("TTS服务未授权")
                self.notify("speech_synthesis_failed", {"text": text, "error": "TTS服务未授权"})
                return None
            elif response.status_code == 429:
                # 请求过多
                self.logger.error("TTS服务请求过多，请稍后再试")
                self.notify("speech_synthesis_failed", {"text": text, "error": "TTS服务请求过多，请稍后再试"})
                return None
            elif response.status_code >= 500:
                # 服务器错误
                self.logger.error(f"TTS服务器错误: {response.status_code}")
                self.notify("speech_synthesis_failed", {"text": text, "error": f"TTS服务器错误: {response.status_code}"})
                return None
            else:
                # 触发语音合成失败事件
                error_msg = f"TTS服务返回错误: {response.status_code}"
                self.notify("speech_synthesis_failed", {"text": text, "error": error_msg})
                self.logger.error(error_msg)
                return None
        except Exception as e:
            self.logger.error(f"合成语音失败: {str(e)}")
            # 触发语音合成失败事件
            self.notify("speech_synthesis_failed", {"text": text, "error": str(e)})
            return None
    
    def synthesize_async(self, 
                       text: str, 
                       speaker: str,
                       output_dir: Optional[str] = None,
                       progress_callback: Optional[Callable[[int, int, str], None]] = None,
                       completion_callback: Optional[Callable[[str, Optional[str]], None]] = None) -> None:
        """
        异步合成语音
        
        @param text: 要合成的文本
        @param speaker: 音色ID，必填参数
        @param output_dir: 输出目录，如果为 None 则使用配置中的app.config_path/speech目录
        @param progress_callback: 进度回调函数，参数为(当前进度, 总进度, 当前处理的文本)
        @param completion_callback: 完成回调函数，参数为(话术文本, 音频文件路径)
        """
        # 检查speaker参数是否提供
        if not speaker:
            error_msg = "合成语音失败: speaker参数为必填项"
            self.logger.error(error_msg)
            self.notify("speech_synthesis_failed", {"text": text, "error": error_msg})
            
            # 如果提供了完成回调，调用它并传递None表示失败
            if completion_callback:
                try:
                    completion_callback(text, None)
                except Exception as e:
                    self.logger.error(f"调用完成回调函数失败: {str(e)}")
            return
        
        # 清理已完成的线程
        self._cleanup_threads()
        
        # 如果线程数达到最大值，等待一个线程完成
        while len(self._synthesis_threads) >= self._max_threads:
            time.sleep(0.1)
            self._cleanup_threads()
        
        # 创建新线程
        thread = threading.Thread(
            target=self._synthesize_async_thread,
            args=(text, speaker, output_dir, progress_callback, completion_callback)
        )
        thread.daemon = True
        thread.start()
        
        # 添加到线程池
        self._synthesis_threads.append(thread)
        
        # 记录使用的实际音色名称
        actual_speaker = self._ensure_speaker_format(speaker)
        
        self.logger.info(f"启动异步语音合成线程，当前线程数: {len(self._synthesis_threads)}, 音色: {actual_speaker}")
    
    def _synthesize_async_thread(self, 
                               text: str, 
                               speaker: str,
                               output_dir: Optional[str],
                               progress_callback: Optional[Callable[[int, int, str], None]],
                               completion_callback: Optional[Callable[[str, Optional[str]], None]]) -> None:
        """
        异步合成线程
        
        @param text: 要合成的文本
        @param speaker: 音色ID
        @param output_dir: 输出目录
        @param progress_callback: 进度回调函数
        @param completion_callback: 完成回调函数
        """
        try:
            # 确保speaker参数格式正确
            formatted_speaker = self._ensure_speaker_format(speaker)
            
            # 同步合成语音
            file_path = self.synthesize(text, formatted_speaker, output_dir)
            
            # 检查是否需要处理音频空白部分（已在synthesize方法中处理）
            # 此处不需要重复处理
            
            # 调用完成回调
            if completion_callback:
                # 检查是否在主线程中
                if threading.current_thread() is threading.main_thread():
                    # 在主线程中直接调用
                    completion_callback(text, file_path)
                else:
                    # 在子线程中，使用信号机制调用
                    try:
                        from PyQt5.QtCore import QObject, pyqtSignal
                        
                        # 创建临时信号发射器
                        class TempSignalEmitter(QObject):
                            single_signal = pyqtSignal(str, object)
                        
                        # 创建信号处理函数
                        def handle_completion(text, path):
                            completion_callback(text, path)
                        
                        # 创建信号发射器
                        temp_emitter = TempSignalEmitter()
                        temp_emitter.single_signal.connect(handle_completion)
                        
                        # 保存引用，避免被垃圾回收
                        self._temp_helper_single = temp_emitter
                        
                        # 发射信号
                        temp_emitter.single_signal.emit(text, file_path)
                    except Exception as e:
                        self.logger.error(f"异步合成回调异常: {str(e)}")
                        # 直接调用回调
                        completion_callback(text, file_path)
        except Exception as e:
            self.logger.error(f"异步合成线程异常: {str(e)}")
            traceback.print_exc()
            
            # 调用完成回调，传递None表示失败
            if completion_callback:
                try:
                    completion_callback(text, None)
                except Exception as callback_error:
                    self.logger.error(f"异步合成回调异常: {str(callback_error)}")
        finally:
            # 从线程池中移除当前线程
            if threading.current_thread() in self._synthesis_threads:
                self._synthesis_threads.remove(threading.current_thread())
    
    def configure(self, config: Dict[str, Any]) -> None:
        """
        配置语音服务
        
        @param config: 配置字典
        """
        # 记录收到的配置，方便调试
        self.logger.debug(f"SpeechService收到配置: {config}")
        
        # 直接处理TTS URL配置
        if "tts_url" in config:
            self._config["tts_url"] = config["tts_url"]
            self.logger.info(f"已设置TTS URL: {config['tts_url']}")
            
            # 同步更新AppConfig
            app_config = AppConfig.get_instance()
            app_config.set("tts", "url", config["tts_url"])
            app_config.save()
        # 处理单独的url参数
        elif "url" in config:
            self._config["tts_url"] = config["url"]
            self.logger.info(f"已设置TTS URL(从url参数): {config['url']}")
            
            # 同步更新AppConfig
            app_config = AppConfig.get_instance()
            app_config.set("tts", "url", config["url"])
            app_config.save()
            
        # 处理是否添加.pt后缀配置
        if "add_pt_suffix" in config:
            self._config["add_pt_suffix"] = config["add_pt_suffix"]
            self.logger.info(f"已设置是否自动添加.pt后缀: {config['add_pt_suffix']}")
        
        # 更新其他配置
        for key, value in config.items():
            if key not in ["tts_url", "url", "add_pt_suffix"]:  # 跳过已处理的键
                self._config[key] = value
        
        # 更新默认音色
        if "default_speaker" in config:
            self._default_speaker = config["default_speaker"]
        
        # 从AppConfig中获取TTS配置
        app_config = AppConfig.get_instance()
        tts_config = app_config.get("tts", {})
        
        # 强制设置最大线程数为1，无论配置如何
        self._max_threads = 1
            
        # 强制设置最大批量生成线程数为1
        self._max_batch_threads = 1
        
        self.logger.debug(f"已配置语音服务: {self._config}")
        self.logger.info(f"语音服务最大线程数已限制为1，同时只生成一个音频")
    
    def set_audio_service(self, audio_service: IAudioService) -> None:
        """
        设置音频服务
        
        @param audio_service: 音频服务实例
        """
        self._audio_service = audio_service
        self.logger.debug("已设置音频服务")
    
    def start(self) -> bool:
        """
        启动服务
        
        @return: 是否成功启动
        """
        if self._running:
            self.logger.warning("语音服务已经在运行")
            return True
        
        try:
            # 设置运行标志
            self._running = True
            
            # 使用辅助方法获取音频缓存目录并确保目录存在
            cache_dir = self._get_audio_cache_dir()
            
            self.logger.info(f"语音服务已启动，缓存目录: {cache_dir}")
            return True
        except Exception as e:
            self.logger.error(f"启动语音服务失败: {str(e)}")
            self._running = False
            return False
    
    def stop(self) -> bool:
        """
        停止服务
        
        @return: 是否成功停止
        """
        if not self._running:
            self.logger.warning("语音服务未在运行")
            return True
        
        try:
            # 清除运行标志
            self._running = False
            
            # 等待所有线程完成
            for thread in self._synthesis_threads:
                if thread.is_alive():
                    thread.join(timeout=1.0)
            
            for thread in self._batch_threads:
                if thread.is_alive():
                    thread.join(timeout=1.0)
            
            # 清空线程池
            self._synthesis_threads.clear()
            self._batch_threads.clear()
            
            self.logger.info("语音服务已停止")
            return True
        except Exception as e:
            self.logger.error(f"停止语音服务失败: {str(e)}")
            return False
    
    def is_ready(self) -> bool:
        """
        检查语音服务是否准备就绪
        
        @return: 是否准备就绪
        """
        return self._running
    
    def shutdown(self) -> None:
        """
        关闭服务
        """
        self.logger.info("语音服务正在关闭...")
        
        # 停止服务
        self.stop()
        
        # 清空监听器
        self.clear_listeners()
        
        self.logger.info("语音服务已关闭")
    
    def _cleanup_threads(self) -> None:
        """
        清理已完成的线程
        """
        # 清理合成线程
        self._synthesis_threads = [t for t in self._synthesis_threads if t.is_alive()]
        
        # 清理批量生成线程
        self._batch_threads = [t for t in self._batch_threads if t.is_alive()]
    
    def _sanitize_filename(self, filename: str) -> str:
        """
        清理文件名，替换不符合文件名规则的字符
        
        @param filename: 原始文件名
        @return: 清理后的文件名
        """
        if not filename:
            return "speech"
        
        # 替换不允许的字符为下划线
        # Windows文件名不允许包含以下字符: \ / : * ? " < > |
        sanitized = re.sub(r'[\\/:*?"<>|]', '_', filename)
        
        # 去除前后空白字符
        sanitized = sanitized.strip()
        
        # 替换连续的下划线为单个下划线
        sanitized = re.sub(r'_+', '_', sanitized)
        
        # 如果文件名为空，使用默认名称
        if not sanitized:
            sanitized = "speech"
        
        # 限制文件名长度，避免路径过长问题
        # 中文字符在文件系统中可能占用更多字节，所以限制更严格
        if len(sanitized) > 50:
            sanitized = sanitized[:50]
        
        return sanitized
    
    def sanitize_filename(self, filename: str) -> str:
        """
        清理文件名，替换不符合文件名规则的字符
        
        @param filename: 原始文件名
        @return: 清理后的文件名
        """
        return self._sanitize_filename(filename)
    
    def _clear_audio_files(self, directory: str) -> None:
        """
        清除目录中的所有非txt文件和子目录中的所有文件
        
        @param directory: 目录路径
        """
        try:
            # 确保目录存在
            if not os.path.exists(directory):
                return
            
            # 遍历目录中的所有文件和子目录
            for root, dirs, files in os.walk(directory):
                # 清除所有非txt文件
                for file in files:
                    if not file.endswith('.txt'):
                        file_path = os.path.join(root, file)
                        try:
                            os.remove(file_path)
                            self.logger.debug(f"已删除文件: {file_path}")
                        except Exception as e:
                            self.logger.error(f"删除文件失败: {file_path}, 错误: {str(e)}")
                
                # 如果是子目录，清除所有文件
                if root != directory:
                    for file in files:
                        file_path = os.path.join(root, file)
                        try:
                            os.remove(file_path)
                            self.logger.debug(f"已删除文件: {file_path}")
                        except Exception as e:
                            self.logger.error(f"删除文件失败: {file_path}, 错误: {str(e)}")
        except Exception as e:
            self.logger.error(f"清除音频文件失败: {str(e)}")
            traceback.print_exc()
    
    def generate_test_filename(self, text: str, speaker: str) -> str:
        """
        生成测试文件名，用于测试文件名生成逻辑
        
        @param text: 要合成的文本
        @param speaker: 音色ID
        @return: 生成的文件名（不含扩展名）
        """
        # 使用处理后的文本内容作为文件名
        text_for_filename = text[:20]  # 取前20个字符
        filename = self._sanitize_filename(text_for_filename)
        
        # 添加音色信息和时间戳确保唯一性
        speaker_short = self._sanitize_filename(speaker)[:10]  # 取音色名称的前10个字符
        timestamp = int(time.time())
        filename = f"{filename}_{speaker_short}_{timestamp}"
        
        return filename 